#include <network.h>
#include <snprintf.h>
#include <string.h>
#include <ntlm.h>
#include <libpay.h>

SSLCtx __SSLCtx;
int __ctimeout=0;
int __rtimeout=0;
int __wtimeout=0;

static char __lastHttpError[1024] = "";

const char* getHttpLastError()
{
	return __lastHttpError;
}


int CreateSSLContext(const char* ssl,const char* cert,const char* key,const char* pass)
{
	SSLCtx::initialize();
	
	char ver[512];
	__snprintf(ver,sizeof(ver),"%s",ssl);
	char* cipher=strpbrk(ver,":/\\");
	if(cipher) {*cipher=0;cipher++;}
	else cipher="";
	
	if(__SSLCtx.create(lstrcmpi(ver,"SSLv2")?SSL3:SSL2))
	{
		snprintf(__lastHttpError, sizeof(__lastHttpError), "can`t initialize SSL context");
		return -1;
	}
	if(*cipher) __SSLCtx.set_cipher(cipher);

	if(cert && key && *cert && *key)
	{
		if(__SSLCtx.set_certificate(cert,key,pass))
		{
			snprintf(__lastHttpError, sizeof(__lastHttpError), "can`t load SSL certificate \"%s\" with key \"%s\"", cert, key);
			__SSLCtx.destroy();
			return -1;
		}
	}
	return 0;
}
int DestroySSLContext(void)
{
	return __SSLCtx.destroy();
}

int SetHttpTimeouts(int c,int r,int w)
{
	__ctimeout=c;
	__rtimeout=r;
	__wtimeout=w;
	return 0;
}

const char* GetNetworkError(int n)
{
	switch(n)
	{
	case HTTP_ERR_SUCCESS: return "success";
	case HTTP_ERR_ARGS: return "bad args";
	case HTTP_ERR_WRITE: return "can`t write";
	case HTTP_ERR_READ: return "can`t read";
	case HTTP_ERR_FORMAT: return "bad response";
	case HTTP_ERR_OUTOFMEM: return "out of memory";
	}
	return "unknown";
}

int SendHttpRequest(const char* agent,const char* url,const char* proxy,const char* request,int nrequest,char** answer)
{
	if(!url) url="";
	if(!proxy) proxy="";
	
	if(!*url || !answer)
	{
		snprintf(__lastHttpError, sizeof(__lastHttpError),"bad SendHttpRequest args");
		return AERR_BADARGS;
	}
	
	int secure=0;
	char host[512]="";
	char service[512]="";
	char resource[512]="";
	char proxy_service[512]="";
	char proxy_login[64]="";
	char proxy_passwd[64]="";
	char proxy_domain[64]="";
	{
// parse url
		char temp[1024];
		static const char SERVICE_TAG[]="://";
		char* ptr=strstr(url,SERVICE_TAG);
		if(ptr)
		{
			char type[32]="";
			int n=ptr-url;
			if(n>=sizeof(type))
				n=sizeof(type)-1;
			strncpy(type,url,n);
			type[n]=0;
			
			if(!lstrcmpi(type,"https"))
				secure=1;			
			url=ptr+sizeof(SERVICE_TAG)-1;
		}

		__snprintf(temp,sizeof(temp),"%s",url);

		char* res=strchr(temp,'/');
		if(res) {*res=0;res++;}
		else res="";

		char* port=strchr(temp,':');
		if(port) {*port=0;port++;}
		else port="";

		__snprintf(host,sizeof(host),"%s",temp);
		__snprintf(service,sizeof(service),"%s:%s",temp,*port?port:(secure?"443":"80"));
		__snprintf(resource,sizeof(resource),"/%s",res);
// parse proxy
		if(*proxy)
		{
			__snprintf(temp,sizeof(temp),"%s",proxy);
			ptr=strchr(temp,'@');
			if(ptr)
			{
				*ptr=0;ptr++;
				char* passwd=strchr(temp,':');
				if(passwd) {*passwd=0;passwd++;}
				else passwd="";
				char* domain="";
				char* login=strpbrk(temp,"\\/");
				if(login) {*login=0;login++;domain=temp;}
				else {login=temp;domain="";}
				__snprintf(proxy_login,sizeof(proxy_login),"%s",login);
				__snprintf(proxy_passwd,sizeof(proxy_passwd),"%s",passwd);
				__snprintf(proxy_domain,sizeof(proxy_domain),"%s",domain);
			}else ptr=temp;
			port=strchr(ptr,':');
			if(port) {*port=0;port++;}
			else port="";

			__snprintf(proxy_service,sizeof(proxy_service),"%s:%s",ptr,*port?port:"8080");
		}

	}

	Socket con;
	con.attach(&__SSLCtx);
	con.set_timeouts(__ctimeout,__rtimeout,__wtimeout);

	int rc=-1;
	if(*proxy_service)
	{
		if(con.open(proxy_service,0))
			snprintf(__lastHttpError, sizeof(__lastHttpError),"can`t connect to proxy \"%s\"",proxy_service);
		else
		{
			if(*proxy_login)
			{
				char authdata[4096]="";
				if(*proxy_domain)
				{
// using NTLM proxy auth type
					buildNtlmAuthRequest(authdata,proxy_login,proxy_domain);
					HTTPANSWER result;
					int n=con.http_send_request(&result,"CONNECT",service,0,0,0,agent,0,0,"NTLM",authdata,0);
					if(n)
						snprintf(__lastHttpError, sizeof(__lastHttpError),"can`t connect to \"%s\" via \"%s\" (%s)",service,proxy_service,GetNetworkError(n));
					else
					{
						if(result.result==200)
							rc=0;
						else
						{
							if(result.result==407)
							{
								int keepalive=0;
								const char* auth="";
								for(LPHTTPHDR hdr=result.header;hdr;hdr=hdr->next)
									if(!lstrcmpi(hdr->name,"Connection") || !lstrcmpi(hdr->name,"Proxy-Connection"))
									{
										if(!lstrcmpi(hdr->value,"Keep-Alive"))
											keepalive=1;											
									}else if(!lstrcmpi(hdr->name,"Proxy-Authenticate"))
										auth=hdr->value;
								auth=strchr(auth,' ');
								if(auth)
								{
									auth++;
									while(*auth==' ')
										auth++;
								}
								if(keepalive && auth)
								{
									buildNtlmAuthResponse(authdata,proxy_login,proxy_passwd,(char*)auth);
									con.http_free_answer(&result);
									n=con.http_send_request(&result,"CONNECT",service,0,0,0,agent,0,0,"NTLM",authdata,0);
									if(n)
										snprintf(__lastHttpError, sizeof(__lastHttpError),"can`t connect to \"%s\" via \"%s\" step2 (%s)",service,proxy_service,GetNetworkError(n));
									else
									{
										if(result.result==200)
											rc=0;
										else
											snprintf(__lastHttpError, sizeof(__lastHttpError),"can`t connect to \"%s\" via \"%s\" step2 (proxy response: %i)",service,proxy_service,result.result);
									}
								}else
									snprintf(__lastHttpError, sizeof(__lastHttpError),"can`t connect to \"%s\" via \"%s\" (proxy response: %i, no challenge)",service,proxy_service,result.result);
							}else
								snprintf(__lastHttpError, sizeof(__lastHttpError),"can`t connect to \"%s\" via \"%s\" (proxy response: %i)",service,proxy_service,result.result);
						}
						con.http_free_answer(&result);
					}
				}else
				{
// using BASIC proxy auth type
					buildBasicAuthRequest(authdata,proxy_login,proxy_passwd);
					HTTPANSWER result;
					int n=con.http_send_request(&result,"CONNECT",service,0,0,0,agent,0,0,"Basic",authdata,0);
					if(n)
						snprintf(__lastHttpError, sizeof(__lastHttpError),"can`t connect to \"%s\" via \"%s\" (%s)",service,proxy_service,GetNetworkError(n));
					else
					{
						if(result.result==200)
							rc=0;
						else
							snprintf(__lastHttpError, sizeof(__lastHttpError),"can`t connect to \"%s\" via \"%s\" (proxy response: %i)",service,proxy_service,result.result);
						con.http_free_answer(&result);
					}
				}
			}else
			{
// no proxy auth
				HTTPANSWER result;
				int n=con.http_send_request(&result,"CONNECT",service,0,0,0,agent,0,0,0,0,0);
				if(n)
					snprintf(__lastHttpError, sizeof(__lastHttpError),"can`t connect to \"%s\" via \"%s\" (%s)",service,proxy_service,GetNetworkError(n));
				else
				{
					if(result.result==200)
						rc=0;
					else
						snprintf(__lastHttpError, sizeof(__lastHttpError),"can`t connect to \"%s\" via \"%s\" (proxy response: %i)",service,proxy_service,result.result);
					con.http_free_answer(&result);
				}
			}
		}
	}else
	{
// no proxy
		rc=con.open(service,0);
		if(rc)
			snprintf(__lastHttpError, sizeof(__lastHttpError),"can`t connect to \"%s\"",service);
	}
	if(rc)
		return AERR_CANTCONNECT;
	if(secure)
	{
		if(con.set_secure())
		{
			snprintf(__lastHttpError, sizeof(__lastHttpError),"can`t create secure channel for connection with \"%s\"",service);
			return AERR_SSLCONNECT;
		}
	}

	HTTPANSWER result;
	rc=con.http_send_request(&result,0,resource,0,request,nrequest,agent,host,0,0,0,1);
	if(rc)
	{
		snprintf(__lastHttpError, sizeof(__lastHttpError),"can`t send request to \"%s\" (%s)",service,GetNetworkError(rc));
		return AERR_CANTSENDREQUEST;
	}
	if(result.result!=200)
	{
		snprintf(__lastHttpError, sizeof(__lastHttpError),"can`t send request to \"%s\" (response %i)",service,result.result);
		con.http_free_answer(&result);
		return AERR_PROCESSREQUEST;
	}
	rc=result.length;
	*answer=result.content;
	result.content=0;
	con.http_free_answer(&result);
	
	return (rc>0) ? rc : AERR_NODATAFOUND;
}

int FreeHttpAnswer(char* answer)
{
	if(answer)
		delete[] answer;
	return 0;
}
